package org.eclipse.gittab.fretboard.internal;

import java.util.concurrent.CopyOnWriteArrayList;

import org.eclipse.gittab.fretboard.Fretboard;
import org.eclipse.gittab.fretboard.FretboardDesign;
import org.eclipse.gittab.fretboard.Fretboard.Configuration;
import org.eclipse.gittab.fretboard.Fretboard.FretboardListener;
import org.eclipse.gittab.fretboard.internal.IFretboardPainter.PaintContext;
import org.eclipse.swt.graphics.Color;

/**
 * Abstract implementation of the {@link Fretboard}. </p> Delegates the drawing
 * routines to a {@link IFretboardPainter}, which are created by the
 * {@link IFretboardPainterFactory}, determined by the {@link Configuration}.
 * </p> This class is also a {@link FretboardListener} that delegates all events
 * to the registered {@link FretboardListener}s.
 * 
 * @author mtreitle
 * 
 */
public class DelegatingFretboard implements Fretboard, FretboardListener {

	private Configuration conf;

	private FretboardDesign design;

	/**
	 * The actual used painter.
	 */
	private IFretboardPainter painter;

	private final IFretboardPainterFactory painterFactory;

	/**
	 * Default {@link FretboardDesign} implementation.
	 */
	private final FretboardDesign defaultDesign;

	// safe listener list
	private final CopyOnWriteArrayList<FretboardListener> listeners = new CopyOnWriteArrayList<FretboardListener>();

	/**
	 * Constructor should be called from a subclass.
	 * 
	 * @param factory
	 * @param defaultDesign
	 */
	public DelegatingFretboard(IFretboardPainterFactory factory,
			FretboardDesign defaultDesign) {
		this.painterFactory = factory;
		this.defaultDesign = defaultDesign;
		this.painter = new NullPainter();
	}

	/**
	 * Checks if all needed input parameters are available for drawing the
	 * fretboard.
	 */
	@Override
	public void paint() {

		if (getConfiguration() != null
				&& getConfiguration().isCompositeAvailable()) {

			clean();

			this.painter = painterFactory.createFretboardPainter(this,
					getConfiguration());

			PaintContext ctx = new PaintContext(getCurrentDesign(),
					getConfiguration());

			if (getConfiguration().isValid()) {
				painter.paint(ctx);
			} else { // invalid configuration
				painter.paintConfigurationInvalid(ctx);
			}

		} else {
			throw new IllegalArgumentException("No configuration provided");
		}
	}

	/**
	 * Gets the currently used {@link IFretboardPainter}.
	 * 
	 * @return a {@link NullPainter} if {@link #paint()} was not called before.
	 */
	public IFretboardPainter getFretboardPainter() {
		return this.painter;
	}

	@Override
	public void setConfiguration(Configuration configuration) {

		if (configuration != null) {
			this.conf = configuration;
		}
	}

	@Override
	public Configuration getConfiguration() {
		return conf;
	}

	@Override
	public FretboardDesign getDefaultDesign() {
		return defaultDesign;
	}

	@Override
	public FretboardDesign getCurrentDesign() {
		return design != null ? design : getDefaultDesign();
	}

	@Override
	public void setDesign(FretboardDesign design) {
		this.design = design;
	}

	@Override
	public void play(int fret, int string) {

		painter.select(fret, string, getCurrentDesign().playNote());
	}

	@Override
	public boolean isPlaying(int fret, int string) {

		Color color = painter.getSelection(fret, string);

		return color != null && color.equals(getCurrentDesign().playNote());
	}

	@Override
	public void clear(int fret, int string) {

		painter.deSelect(fret, string);
	}

	@Override
	public void clearAll() {

		for (int f = 1; f <= getConfiguration().getFretsNr(); f++) {
			for (int s = 1; s <= getConfiguration().getStringsNr(); s++) {
				clear(f, s);
			}
		}
	}

	@Override
	public void addFretboardListener(FretboardListener listener) {
		listeners.add(listener);
	}

	@Override
	public void removeFretboardListener(FretboardListener listener) {
		listeners.remove(listener);
	}

	public final void fireEvent(int fret, int string) {

		for (FretboardListener l : listeners) {
			l.clickedOn(fret, string);
		}
	}

	@Override
	public void clickedOn(int fret, int string) {
		fireEvent(fret, string);
	}

	public void clean() {

		painter.clean();
		// painter = new NullPainter();
	}

	public void dispose() {
		clean();
	}

	/**
	 * Does nothing.
	 * 
	 * @author mtreitle
	 * 
	 */
	class NullPainter implements IFretboardPainter {

		@Override
		public void clean() {
		}

		@Override
		public void deSelect(int fret, int string) {
		}

		@Override
		public Color getSelection(int fret, int string) {
			return null;
		}

		@Override
		public void paint(PaintContext ctx) {
		}

		@Override
		public void select(int fret, int string, Color color) {
		}

		@Override
		public void paintConfigurationInvalid(PaintContext ctx) {
		}

	}
}
